﻿using System;
using System.Collections.Generic;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using Jage.Common;
using Jage.GameEditor.Controls.Resources;
using Jage.Logic.Resources;
using Jage.Win32;
using Microsoft.Xna.Framework;
using Point = System.Drawing.Point;

namespace Jage.GameEditor.Controls.Spritesheets
{
    public partial class SpritesheetEditor : Form
    {
        public SpritesheetEditor()
        {
            InitializeComponent();
        }

        #region Methods

        private ListViewDatabinder<Data.SpriteSource> _spritesDatabinder;

        private void DefineSpritesDatabinder()
        {
            _spritesDatabinder = new ListViewDatabinder<Data.SpriteSource>(listViewSprites, "SpriteSourceId");
            _spritesDatabinder.AddColumnDefinition("SortIndex");
            _spritesDatabinder.AddColumnDefinition((args) => Path.GetFileName((string)args[0]), "Resource");
            _spritesDatabinder.AddColumnDefinition((args) => string.Format("{0}x{1}", args[0], args[1]), "Width", "Height");
            _spritesDatabinder.AddColumnDefinition((args) => string.Format("{0}x{1}", args[0], args[1]), "OriginX", "OriginY");
            _spritesDatabinder.AddColumnDefinition((args) => string.Format("{0},{1}-{2},{3}", args[0], args[1], (int)args[2] - (int)args[0], (int)args[3] - (int)args[1]), "CropX", "CropY", "CropWidth", "CropHeight");
            _spritesDatabinder.AddColumnDefinition((args) => Path.GetDirectoryName((string)args[0]), "Resource");
        }

        private void RefreshSprites()
        {
            _spritesDatabinder.Databind(from sprite in Data.JageDb.Default.SpriteSources where sprite.SpritesheetId == Spritesheet.SpritesheetId orderby sprite.SortIndex, sprite.Resource select sprite);
            var sprites = from s in Data.JageDb.Default.Sprites
                          where s.SpritesheetId == Spritesheet.SpritesheetId
                          select s;
            var isInUse = Enumerable.Any(sprites, sprite => sprite.AnimationSprites.Count > 0);
            toolStrip2.Enabled = !isInUse;
            labelInUse.Visible = isInUse;
            if (isInUse)
            {
                IEnumerable<Guid> animationIds = from animsprite in Data.JageDb.Default.AnimationSprites
                                                 where
                                                     (from sprite in sprites select sprite.SpriteId).Contains(
                                                         animsprite.SpriteId)
                                                 select animsprite.AnimationId;
                labelInUse.Text = string.Format("Dependent animations: {0}",
                                                string.Join(", ",
                                                            from a in Data.JageDb.Default.Animations
                                                            where animationIds.Contains(a.AnimationId)
                                                            select a.Name));
            }
        }

        #endregion

        #region Eventhandlers

        private void SpritesheetEditor_Load(object sender, EventArgs e)
        {
            DefineSpritesDatabinder();
        }

        private void SpritesheetEditor_Shown(object sender, EventArgs e)
        {
            textBoxName.Text = Spritesheet.Name;
            RefreshSprites();
        }

        private void ButtonAddSpritesClick(object sender, EventArgs e)
        {
            var browser = new ResourceBrowser();
            browser.AllowedResourceTypes.Add(ResourceType.Sprite);
            if (browser.ShowDialog() == DialogResult.OK)
            {
                BusyBox.ShowBusy("Adding and cropping new sprites...");
                try
                {
                    AddSprites(browser.SelectedResources, Spritesheet.SpritesheetId);
                    RefreshSprites();
                }
                finally
                {
                    BusyBox.HideBusy();
                }
            }
        }

        private void AddSprites(IEnumerable<string> resources, Guid spritesheetId)
        {
            int? sortIndexOrNull = (from s in Data.JageDb.Default.SpriteSources where s.SpritesheetId == spritesheetId select s).Max((s) => (int?)s.SortIndex);
            int sortIndex = sortIndexOrNull.GetValueOrDefault();
            foreach (var f in resources)
            {
                var bitmap = ResourceIO.GetBitmap(f);
                Rectangle crop = bitmap.GetNonTransparentRectangle();
                Data.JageDb.Default.SpriteSources.AddObject(Logic.Crud.SpriteSource.GetNew(spritesheetId, ++sortIndex, f, bitmap.Width, bitmap.Height, crop));
            }
            Data.JageDb.Default.SaveChanges();
        }

        private void listViewSprites_SelectedIndexChanged(object sender, EventArgs e)
        {
            buttonDelete.Enabled = listViewSprites.SelectedItems.Count > 0;
            if (_spritesDatabinder.SelectedObjects.Count() > 0)
            {
                var sprite = _spritesDatabinder.SelectedObjects.First();
                spritePreview1.SetResource(sprite.Resource, new Point(sprite.OriginX, sprite.OriginY));
            }
        }

        private void buttonDeleteSprites_Click(object sender, EventArgs e)
        {
            if (listViewSprites.SelectedItems.Count > 0 && MessageBox.Show(string.Format("Delete {0} sprites?", listViewSprites.SelectedItems.Count), "Confirm", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
            {
                foreach (var spriteSource in _spritesDatabinder.SelectedObjects)
                {
                    Data.JageDb.Default.DeleteObject(spriteSource);
                }
                Data.JageDb.Default.SaveChanges();
                CleanSortIndex();
                RefreshSprites();
            }
        }

        private void CleanSortIndex()
        {
            var number = 1;
            foreach (var sprite in (from s in Data.JageDb.Default.Sprites where s.SpritesheetId == Spritesheet.SpritesheetId orderby s.SortIndex select s))
            {
                if (sprite.SortIndex != number) sprite.SortIndex = number;
                number++;
            }
            Data.JageDb.Default.SaveChanges();
        }

        private void buttonMoveUp(object sender, EventArgs e)
        {
            // save selection of guids, because records will change listviewitems, selection must follow the records, not the listviewitems.
            var selection = _spritesDatabinder.SelectedObjects;
            foreach (int index in listViewSprites.SelectedIndices)
            {
                SwitchSprites(index, index - 1);
            }
            _spritesDatabinder.SelectedObjects = selection; // reapply original Guid selection
            Data.JageDb.Default.SaveChanges();
            RefreshSprites();
        }

        private void buttonMoveDown(object sender, EventArgs e)
        {
            // save selection of guids, because records will change listviewitems, selection must follow the records, not the listviewitems.
            var selection = _spritesDatabinder.SelectedObjects;
            for (int i = listViewSprites.SelectedIndices.Count - 1; i >= 0; i--)
            {
                var index = listViewSprites.SelectedIndices[i];
                SwitchSprites(index, index + 1);
            }
            _spritesDatabinder.SelectedObjects = selection; // reapply original Guid selection
            Data.JageDb.Default.SaveChanges();
            RefreshSprites();
        }

        private void SwitchSprites(int index1, int index2)
        {
            if (index1 >= 0 && index1 < listViewSprites.Items.Count && index2 >= 0 && index2 < listViewSprites.Items.Count)
            {
                var sprite1 = listViewSprites.Items[index1].Tag as Data.Sprite;
                var sprite2 = listViewSprites.Items[index2].Tag as Data.Sprite;
                if (sprite1 != null && sprite2 != null)
                {
                    buttonSortAlpha.Checked = false;
                    buttonSortNumeric.Checked = false;
                    buttonManual.Checked = true;

                    // swap SortIndex
                    int temp = sprite1.SortIndex;
                    sprite1.SortIndex = sprite2.SortIndex;
                    sprite2.SortIndex = temp;

                    // swap places
                    listViewSprites.Items[index1].Tag = sprite2;
                    listViewSprites.Items[index2].Tag = sprite1;
                }
            }
        }

        private void sortAlphabeticallyToolStripMenuItem_Click(object sender, EventArgs e)
        {
            buttonSortAlpha.Checked = true;
            buttonSortNumeric.Checked = false;

            ReorderSortIndices();
        }

        private void ReorderSortIndices()
        {
            var sortIndex = 1;
            foreach (var sprite in (from s in Data.JageDb.Default.SpriteSources where s.SpritesheetId == Spritesheet.SpritesheetId select s).ToList().OrderBy(GetSortValue))
            {
                if (sprite.SortIndex != sortIndex) sprite.SortIndex = sortIndex;
                sortIndex++;
            }
            Data.JageDb.Default.SaveChanges();
            RefreshSprites();
        }

        private void sortNumericallydetectNumberToolStripMenuItem_Click(object sender, EventArgs e)
        {
            buttonSortAlpha.Checked = false;
            buttonSortNumeric.Checked = true;

            ReorderSortIndices();
        }

        private object GetSortValue(Data.SpriteSource sprite)
        {
            if (buttonSortAlpha.Checked)
            {
                return Path.GetFileName(sprite.Resource);
            }
            else
            {
                var regexNumber = new Regex("\\d+");
                var match = regexNumber.Match(Path.GetFileName(sprite.Resource));
                return match.Success ? int.Parse(match.Value) : 0;
            }
        }

        private void selectAllToolStripMenuItem_Click(object sender, EventArgs e)
        {
            _spritesDatabinder.SelectAll();
        }

        #endregion

        #region Properties

        public Data.Spritesheet Spritesheet { get; set; }

        #endregion

        private void SpritesheetEditor_FormClosing(object sender, FormClosingEventArgs e)
        {
            e.Cancel = String.IsNullOrWhiteSpace(textBoxName.Text) || (from s in Data.JageDb.Default.Spritesheets where s.SpritesheetId != Spritesheet.SpritesheetId && s.Name.ToLower() == textBoxName.Text.ToLower() select s).Count() > 0;
            if (e.Cancel)
            {
                MessageBox.Show(@"A spritesheet must have a unique name.", @"Invalid spritesheet", MessageBoxButtons.OK, MessageBoxIcon.Warning);
            }
            else
            {
                if (_spritesDatabinder.ObjectCount > 0)
                {
                    try
                    {
                        var dontSave = false;

                        if (Enumerable.Any(from s in Data.JageDb.Default.Sprites
                                           where s.SpritesheetId == Spritesheet.SpritesheetId
                                           select s, sprite => sprite.AnimationSprites.Count > 0))
                        {
                            dontSave = true;
                        }

                        if (!dontSave)
                        {
                            BusyBox.ShowBusy("Generating spritesheet...");

                            if (File.Exists(ResourceIO.GetAbsolute(Spritesheet.Resource)))
                            {
                                File.Delete(ResourceIO.GetAbsolute(Spritesheet.Resource));
                            }

                            Spritesheet.Name = textBoxName.Text;
                            Spritesheet.Resource = string.Format("_generated\\Spritesheets\\{0}.png",
                                                                     Spritesheet.Name);

                            var spritesheetPng = Logic.Sprites.SpritesheetPngFactory.Get(Spritesheet);
                            Directory.CreateDirectory(ResourceIO.GetAbsolute("_generated\\Spritesheets"));
                            spritesheetPng.Bitmap.Save(ResourceIO.GetAbsolute(Spritesheet.Resource), ImageFormat.Png);

                            foreach (
                                var sprite in
                                    (from s in Data.JageDb.Default.Sprites
                                     where s.SpritesheetId == Spritesheet.SpritesheetId
                                     select s))
                            {
                                Data.JageDb.Default.DeleteObject(sprite);
                            }

                            foreach (var sprite in spritesheetPng.Sprites)
                            {
                                Data.JageDb.Default.Sprites.AddObject(Logic.Crud.Sprite.GetNew(
                                    Spritesheet.SpritesheetId, sprite.RectangleInSheet, sprite.Origin, sprite.SortIndex));
                            }
                            BusyBox.HideBusy();
                        }
                        Data.JageDb.Default.SaveChanges();
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show(ex.Message, "Cannot edit", MessageBoxButtons.OK,
                                        MessageBoxIcon.Error);
                        e.Cancel = true;
                    }
                }
            }
        }

        private void buttonOriginLeft_Click(object sender, EventArgs e)
        {
            MoveOrigins(-1, 0);
        }

        private void MoveOrigins(int deltaX, int deltaY)
        {
            foreach (var sprite in _spritesDatabinder.AllObjects)
            {
                sprite.OriginX += deltaX;
                sprite.OriginY += deltaY;
            }
            Data.JageDb.Default.SaveChanges();
            RefreshSprites();
        }

        private void buttonOriginRight_Click(object sender, EventArgs e)
        {
            MoveOrigins(1, 0);
        }

        private void buttonOriginUp_Click(object sender, EventArgs e)
        {
            MoveOrigins(0, -1);
        }

        private void buttonOriginDown_Click(object sender, EventArgs e)
        {
            MoveOrigins(0, 1);
        }

    }
}
